﻿using ChoWorkflowServer.Model;
using Cinchoo.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChoWorkflowServer.Model
{
    public class ChoClusterManager
    {
        public static readonly ChoClusterManager Default = new ChoClusterManager();
        public const string ANY_CLUSTER_NAME = "<ANY>";

        private readonly object _padLock = new object();
        private readonly Dictionary<string, HashSet<string>> _clusters = new Dictionary<string, HashSet<string>>();
        private readonly Dictionary<string, IEnumerator<string>> _clusterLBs = new Dictionary<string, IEnumerator<string>>();

        public event EventHandler<ChoEventArgs<string>> NodeAdd = delegate { };
        public event EventHandler<ChoEventArgs<string>> NodeRemove = delegate { };
        public event EventHandler<EventArgs> ClusterReset = delegate { };

        public string Resolve(string name)
        {
            if (name.IsNullOrWhiteSpace()) return name;

            name = name.ToUpper();
            if (name == ANY_CLUSTER_NAME)
            {
                lock (_padLock)
                {
                    IEnumerator<string> lb = GetClusterLBIfAny(name);
                    lb.MoveNext();
                    return lb.Current;
                }
                //return Environment.MachineName;
            }
            else
                return name;
        }

        public void Reset()
        {
            lock (_padLock)
            {
                _clusters.Clear();
                _clusterLBs.Clear();
                ClusterReset.Raise(null, null);
            }
        }

        public void AddNode(string nodeName)
        {
            if (nodeName.IsNullOrWhiteSpace()) return;
            AddNodeToCluster(ANY_CLUSTER_NAME, nodeName);
        }

        public void AddNodes(string[] nodeNames)
        {
            if (nodeNames == null) return;
            AddNodesToCluster(ANY_CLUSTER_NAME, nodeNames);
        }

        public void RemoveNode(string nodeName)
        {
            if (nodeName.IsNullOrWhiteSpace()) return;
            RemoveNodeFromCluster(ANY_CLUSTER_NAME, nodeName);
        }

        public void RemoveNodes(string[] nodeNames)
        {
            if (nodeNames == null) return;

            RemoveNodesFromCluster(ANY_CLUSTER_NAME, nodeNames);
        }

        public void AddNodeToCluster(string clusterName, string nodeName, bool isPrivate = false)
        {
            if (clusterName.IsNullOrWhiteSpace() || nodeName.IsNullOrWhiteSpace()) return;

            lock (_padLock)
            {
                clusterName = clusterName.ToUpper();
                nodeName = nodeName.ToUpper();

                if (!_clusters.ContainsKey(clusterName))
                {
                    _clusters.Add(clusterName, new HashSet<string>());
                    NodeAdd.Raise(null, new ChoEventArgs<string>(clusterName));
                }

                if (!_clusters[clusterName].Contains(nodeName) /*&& FindClusterForNode(nodeName).IsNullOrWhiteSpace()*/)
                {
                    _clusters[clusterName].Add(nodeName);
                    NodeAdd.Raise(null, new ChoEventArgs<string>(nodeName));
                }

                ResetClusterLB(clusterName);

                if (clusterName != ANY_CLUSTER_NAME && !isPrivate)
                    AddNodeToCluster(ANY_CLUSTER_NAME, nodeName);
            }
        }

        public IEnumerable<string> FindClustersForNode(string nodeName)
        {
            lock (_padLock)
            {
                nodeName = nodeName.ToUpper();
                foreach (string clusterName in _clusters.Keys.ToArray())
                {
                    if (_clusters[clusterName].Contains(nodeName))
                        yield return clusterName;
                }
            }
        }

        public void AddNodesToCluster(string clusterName, string[] nodeNames, bool isPrivate = false)
        {
            if (clusterName.IsNullOrWhiteSpace() || nodeNames.IsNull()) return;

            foreach (string nodeName in nodeNames)
                AddNodeToCluster(clusterName, nodeName, isPrivate);
        }

        //public void AddOrUpdateCluster(string clusterName, string[] nodeNames)
        //{
        //    if (clusterName.IsNullOrWhiteSpace()) return;
        //    if (nodeNames == null) return;

        //    lock (_padLock)
        //    {
        //        if (_clusters.ContainsKey(clusterName))
        //            _clusters[clusterName].Clear();
        //        else
        //            _clusters.Add(clusterName, new HashSet<string>());

        //        foreach (string nodeName in nodeNames)
        //        {
        //            if (!_clusters[clusterName].Contains(nodeName))
        //                _clusters[clusterName].Add(nodeName);
        //        }
        //        ResetClusterLB(clusterName);
        //    }
        //}

        public void RemoveCluster(string clusterName)
        {
            if (clusterName.IsNullOrWhiteSpace()) return;

            clusterName = clusterName.ToUpper();

            if (!_clusters.ContainsKey(clusterName)) return;

            lock (_padLock)
            {
                if (_clusters.ContainsKey(clusterName))
                {
                    foreach (string nodeName in _clusters[clusterName])
                    {
                        NodeRemove.Raise(null, new ChoEventArgs<string>(nodeName));
                    }
                    _clusters.Remove(clusterName);
                    NodeRemove.Raise(null, new ChoEventArgs<string>(clusterName));
                }
            }
        }

        public void RemoveNodeFromCluster(string clusterName, string nodeName)
        {
            if (clusterName.IsNullOrWhiteSpace() || nodeName.IsNullOrWhiteSpace()) return;

            clusterName = clusterName.ToUpper();
            nodeName = nodeName.ToUpper();

            if (!_clusters.ContainsKey(clusterName)) return;

            lock (_padLock)
            {
                if (_clusters.ContainsKey(clusterName) && _clusters[clusterName].Contains(nodeName))
                {
                    _clusters[clusterName].Remove(nodeName);
                    NodeRemove.Raise(null, new ChoEventArgs<string>(nodeName));

                    if (_clusters[clusterName].Count == 0)
                        RemoveCluster(clusterName);

                    ResetClusterLB(clusterName);
                }
            }
        }

        public void RemoveNodesFromCluster(string clusterName, string[] nodeNames)
        {
            if (clusterName.IsNullOrWhiteSpace() || nodeNames.IsNull()) return;

            foreach (string nodeName in nodeNames)
                RemoveNodeFromCluster(clusterName, nodeName);
        }

        public string[] AllClusters
        {
            get
            {
                lock (_padLock)
                {
                    List<string> clusterNames = new List<string>();
                    foreach (string clusterName in _clusters.Keys)
                    {
                        if (clusterName == ANY_CLUSTER_NAME)
                            clusterNames.AddRange(_clusters[ANY_CLUSTER_NAME].ToArray());
                        else
                            clusterNames.Add(clusterName);
                    }

                    return clusterNames.ToArray();
                }
            }
        }

        public KeyValuePair<string, string>[] AllClusterNodePairs
        {
            get
            {
                lock (_padLock)
                {
                    List<KeyValuePair<string, string>> clusterNodePairs = new List<KeyValuePair<string, string>>();
                    foreach (string clusterName in _clusters.Keys)
                    {
                        foreach (string nodeName in _clusters[clusterName])
                            clusterNodePairs.Add(new KeyValuePair<string, string>(clusterName, nodeName));
                    }

                    return clusterNodePairs.ToArray();
                }
            }
        }

        private IEnumerator<string> GetClusterLBIfAny(string nodeName)
        {
            lock (_padLock)
            {
                foreach (string clusterName in _clusters.Keys)
                {
                    if (clusterName == nodeName)
                        return _clusterLBs[clusterName];
                }

                return _clusterLBs[ANY_CLUSTER_NAME];
            }
        }

        private void ResetClusterLB(string clusterName)
        {
            lock (_padLock)
            {
                if (_clusterLBs.ContainsKey(clusterName))
                    _clusterLBs.Remove(clusterName);

                if (_clusters.ContainsKey(clusterName))
                    _clusterLBs.Add(clusterName, _clusters[clusterName].Cycle().GetEnumerator());
            }
        }
    }
}
